﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Collections;
using System.Collections.ObjectModel;

namespace WriteCSV
{
	public class CSVDocument
	{
		public CSVDocument()
		{
			Delimiter = Thread.CurrentThread.CurrentCulture.TextInfo.ListSeparator;
		}

		public string Delimiter { get; set; }

		public int Cells { get { return _Cells.Count; } }
		public int RowCount { get { return _Rows.Count; } }
		public int ColumnCount { get { return _Columns.Count; } }

		public int? MinRow { get; private set; }
		public int? MinColumn { get; private set; }
		public int? MaxRow { get; private set; }
		public int? MaxColumn { get; private set; }

		/// <summary>
		/// Escribe el documento en una interfaz compatible
		/// </summary>
		/// <param name="o"></param>
		public void Save(TextWriter o)
		{
			var row = 0;
			bool first = true;
			foreach (var cell in _Cells)
			{
				if (first)
				{
					first = false;
					row = cell.Key.Row;
				}
				else if (cell.Key.Row != row)
				{
					o.WriteLine();
					row = cell.Key.Row;
				}
				else
				{
					o.Write(Delimiter);
				}
				if (cell.Value == null) continue;
				var str = cell.Value.ToString();

				if (str.Contains(' ') || str.Contains(',') || str.Contains(Delimiter)) str = Quote(str);
				o.Write(str);
			}
		}

		/// <summary>
		/// Guarda el archivo
		/// </summary>
		/// <param name="fn"></param>
		public void Save(string fn)
		{
			using (var s = new StreamWriter(fn))
			{
				Save(s);
				s.Close();
			}
		}

		private string Quote(string str)
		{
			str = str.Replace("\"", "\"\"");
			return '\"' + str + '\"';
		}

		public object this[int row, int col]
		{
			get
			{
				var s = new CellPosition(row, col);
				object val = null;
				_Cells.TryGetValue(s, out val);
				return val;
			}
			set
			{
				var s = new CellPosition(row, col);
				Add(value, s);
			}
		}

		public object this[CellPosition s]
		{
			get
			{
				object val = null;
				_Cells.TryGetValue(s, out val);
				return val;
			}
			set
			{
				Add(value, s);
			}
		}

		SortedDictionary<CellPosition, object> _Cells = new SortedDictionary<CellPosition, object>();
		HashSet<int> _Rows = new HashSet<int>();
		HashSet<int> _Columns = new HashSet<int>();

		public IEnumerable<int> Rows { get { return _Rows; } }
		public IEnumerable<int> Columns { get { return _Columns; } }

		public ICollection<CellPosition> Keys { get { return _Cells.Keys; } }

		private void Add(object value, CellPosition s)
		{
			if (!MaxColumn.HasValue) MaxColumn = s.Column;
			else MaxColumn = Math.Max(MaxColumn.Value, s.Column);
			if (!MaxRow.HasValue) MaxRow = s.Row;
			else MaxRow = Math.Max(MaxRow.Value, s.Row);

			if (!MinColumn.HasValue) MinColumn = s.Column;
			else MinColumn = Math.Min(MinColumn.Value, s.Column);
			if (!MinRow.HasValue) MinRow = s.Row;
			else MinRow = Math.Min(MinRow.Value, s.Row);

			_Rows.Add(s.Row);
			_Columns.Add(s.Column);
			_Cells[s] = value;
		}

		public void AddHorizontalRange(IEnumerable l, int row, int column)
		{
			int i = column;
			foreach (var item in l)
			{
				var pos = new CellPosition(row, i++);
				Add(item, pos);
			}
		}

		public void AddVerticalRange(IEnumerable l, int row, int column)
		{
			int i = row;
			foreach (var item in l)
			{
				var pos = new CellPosition(i++, column);
				Add(item, pos);
			}
		}

		public void AddRange(CSVDocument document, int row, int column, int minRow = 0, int minColumn = 0, int maxRow = int.MaxValue, int maxColumn = int.MaxValue)
		{
			int i = row;
			foreach (var cell in document._Cells)
			{
				var s = cell.Key;
				if (s.Column < minColumn || s.Column > maxColumn || s.Row < minRow || s.Row > maxRow) continue;
				var ss = new CellPosition(s.Row - minRow + row, s.Column - minColumn + column);
				Add(cell.Value, ss);
			}
		}
	}

	public struct CellPosition : IEquatable<CellPosition>, IComparable<CellPosition>
	{
		public CellPosition(int row, int column)
		{
			_Row = row;
			_Column = column;
		}
		int _Row;
		int _Column;

		public int Row { get { return _Row; } }
		public int Column { get { return _Column; } }

		#region IEquatable<CellPosition> Members

		public bool Equals(CellPosition other)
		{
			return other._Column == _Column && other._Row == _Row;
		}

		public override bool Equals(object obj)
		{
			if (obj == null) return false;
			if (!(obj is CellPosition)) return false;
			var other = (CellPosition)obj;
			return other._Column == _Column && other._Row == _Row;
		}

		public override int GetHashCode()
		{
			return _Column.GetHashCode() << 3 & _Row.GetHashCode();
		}

		#endregion

		#region IComparable<CellPosition> Members

		public int CompareTo(CellPosition other)
		{
			var r = _Row - other._Row;
			if (r == 0)
			{
				return _Column - other._Column;
			}
			return r;
		}

		#endregion

		public override string ToString()
		{
			return "" + _Row + ":" + _Column + "";
		}
	}
}
